#include "FTPSession.h"

#include <filesystem>

int FTPSession::send(const FTPFile& flie)
{
	return 0;
}

void FTPSession::send(int code, const std::string& msg)
{
	std::string reply = fmt::format("{} {}\r\n",code,msg);
	cmdConn_->send(msg.c_str(), reply.size());
	std::string log = fmt::format("send: {} {} -> {}", code, msg, cmdConn_->name());
	LOG_DEBUG << log;
}

int FTPSession::recv(char* buf, int maxLen)
{
	return 0;
}

int FTPSession::recv(const FTPFile& flie)
{
	return 0;
}

bool FTPSession::checkLoginState()
{
	if (loginState_ != State::LOGINED) {
		send(ReplyCode::NOT_LOGGED_IN, "User not login.");
		return false;
	}
	return true;
}

bool FTPSession::checkPermission(const std::string filePath)
{
	return true;
}

FTPSession::FTPSession(const std::shared_ptr<TcpConnection>& conn)
	:name_(conn->name()),
	cmdConn_(conn),
	dataConn_(nullptr),
	dataAcceptor_(nullptr),
	file_(new FTPFile),
	loginState_(State::UNLOGIN),
	dataListenAddr_(conn->localAddress(), 0),
	user_(new FTPUser),
	isTransffering_(false)
{
	dataAcceptor_ = std::unique_ptr<evconnlistener, std::function<void(evconnlistener*)>>(
		evconnlistener_new_bind(
			cmdConn_->getBase(),
			static_cast<evconnlistener_cb>([](evconnlistener* listener, socket_t sockfd, sockaddr* addr, int socklen, void* arg)->void
				{
					FTPSession* pthis = static_cast<FTPSession*>(arg);
					pthis->listenCallback(listener, sockfd, addr, socklen, arg);
				}),
			this, LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE | LEV_OPT_THREADSAFE, 1, (struct sockaddr*)&dataListenAddr_, sizeof(dataListenAddr_)),
		[](evconnlistener* listener) { evconnlistener_free(listener); });
	evconnlistener_disable(dataAcceptor_.get());
}

void FTPSession::listenCallback(evconnlistener* listener, socket_t sockfd, sockaddr* addr, int socklen, void*)
{
	// 如果以及创建了dataConn_，向客户端发送data连接已经建立，而不是继续disable listen
	Socket* pSock = new Socket(sockfd);
	if (dataConn_) {
		send(ReplyCode::NOT_OPEN_DATA_CONNECTION,"Data connection is opened.");
		delete pSock;
		return;
	}
	// 创建TcpConnection
	evutil_make_socket_nonblocking(sockfd);
	dataConn_ = std::make_shared<TcpConnection>(cmdConn_->getBase(), pSock, true);
	// 创建数据连接后，默认不监听读写事件。接收到RETR、STOR等指令设置回调并设置enable读写
	dataConn_->disableWrite();
	dataConn_->setCloseCallback([&](const std::shared_ptr<TcpConnection>& conn) {
		send(ReplyCode::CLOSING_DATA_CONNECTION, "Data connection close, transffer stop.");
		if (fileStateMap_.find(file_->filename_) != fileStateMap_.end()) { // 清除文件状态
			fileStateMap_.erase(file_->filename_);
		}
		file_->close();
		dataConn_.reset();
		dataConn_ = nullptr;
		isTransffering_ = false;
		});
	send(ReplyCode::DATA_CONNECTION_READY_OPEN, "Data connection ready open, transffer starting.");
	std::cout << dataConn_->enableEventMsg() << std::endl;
	LOG_DEBUG << "accept new data connection " << dataConn_->name();
}

void FTPSession::writeFileCallback(const std::shared_ptr<TcpConnection>& conn)
{
	startSendFile();
}

void FTPSession::readFileCallback(const std::shared_ptr<TcpConnection>& conn, Buffer& inBuffer)
{
	startRecvFile(inBuffer);
}

void FTPSession::startSendFile()
{
	if (!dataConn_) {
		send(ReplyCode::CONNECTION_CLOSE, "Data connetion close, transffer abort.");
		return;
	}
	else if (dataConn_ && !file_->isWriteFinish()) {
		int n = file_->read(buffer_, BLOCK_SIZE);
		dataConn_->send(buffer_, n);
	}
	else { // dataConn_ && file_->isWriteFinish
		dataConn_->shutdown(); // 关闭写端，由dataConn的closeCallback重置dataConn_、file_等变量
	}
}

void FTPSession::startRecvFile(Buffer& inBuffer)
{
	std::string data = inBuffer.retrieveAll();
	file_->write(data.c_str(), data.size());
}

bool FTPSession::parseCmd(const std::string& cmdlines)
{
	std::string cmdline = "";
	size_t beg = 0, end = cmdlines.size(), CRLF = beg;
	while ((CRLF = cmdlines.find("\r\n", beg)) != std::string::npos) {
		cmdline = cmdlines.substr(beg, CRLF - beg);
		beg = CRLF + 2;
		size_t sep = cmdline.find(" ");
		std::string cmd, param;
		if (sep != std::string::npos) {
			cmd = cmdline.substr(0, sep);
			param = cmdline.substr(sep + 1, cmdline.size() - sep - 1);
		}
		else {
			cmd = cmdline;
			param = "";
		}
		handleCmd(cmd, param);
	}
	return true;
}

bool FTPSession::handleCmd(std::string cmd, const std::string& param)
{
	LOG_DEBUG << "recv command from " << cmdConn_->name() << " cmd: " << cmd << " param: " << param;
	std::for_each(cmd.begin(), cmd.end(), [](char& c) {c = std::toupper(c); });
	// note: 这里不能定义成static const，因为每个对象的this都是不同的，如果使用了static const会导致所有的this是同一个TcpConnection，导致所有的信息都发送到一个TcpConnection去。
	// bug: static const 导致的所有的信息都发送到一个TcpConnection去
	const std::map<std::string, std::function<bool(const std::string&)>> handleMap = {
		// ACCESS CONTROL COMMANDS
		{"USER",std::bind(&FTPSession::handleUSER,this,std::placeholders::_1) },
		{"PASS",std::bind(&FTPSession::handlePASS,this,std::placeholders::_1) },
		// TRANSFER PARAMETER COMMANDS
		{"PASV",std::bind(&FTPSession::handlePASV,this,std::placeholders::_1) },  // PASV 进入PASV模式
		{"TYPE",std::bind(&FTPSession::handleTYPE,this,std::placeholders::_1) }, // TYPE I，设置传输文件的类型。只支持二进制，即参数I
		{"STRU",std::bind(&FTPSession::handleSTRU,this,std::placeholders::_1) },  // STRU F，File structure 把文件看成连续字节流，无内部结构。只支持F参数
		{"MODE",std::bind(&FTPSession::handleMODE,this,std::placeholders::_1) },  // MODE STREAM，只支持STREAM模式
		// FTP SERVICE COMMANDS
		{"RETR",std::bind(&FTPSession::handleRETR,this,std::placeholders::_1) },  // RETRIEVE, RETR file 请求下载文件 
		{"STOR",std::bind(&FTPSession::handleSTOR,this,std::placeholders::_1) },  // STORE, STOR file 请求上传文件，如果文件存在则覆盖，不存在则创建新文件
		{"REST",std::bind(&FTPSession::handleREST,this,std::placeholders::_1)},	  // RESTART, REST offset 设置文件偏移量，用于文件上传、下载的断点续传
		{"RNFR",std::bind(&FTPSession::handleRNFR,this,std::placeholders::_1) },  // RNFR oldname 重命名文件，后面要紧跟一条RNTO语句
		{"RNTO",std::bind(&FTPSession::handleRNTO,this,std::placeholders::_1)},   // RNTO newname 重命名文件为
		{"DELE",std::bind(&FTPSession::handleDELE,this,std::placeholders::_1)},   // 删除文件
		{"PWD",std::bind(&FTPSession::handlePWD,this,std::placeholders::_1)},	  // 当前文件夹
		{"LIST",std::bind(&FTPSession::handleLIST,this,std::placeholders::_1) },
		{"SIZE",std::bind(&FTPSession::handleSIZE,this,std::placeholders::_1)},   // SIZE, SIZE file 获取文件大小，用于文件上传、下载的断点续传
		{"NOOP",std::bind(&FTPSession::handleNOOP,this,std::placeholders::_1)},	  // 什么都不做，返回ok
		{"HELP",std::bind(&FTPSession::handleHELP,this,std::placeholders::_1) }
	};
	auto handler = handleMap.find(cmd);
	if (handler == handleMap.end()) {
		send(ReplyCode::SYNTAX_ERROR_UNRECOGNIZED_COMMAND, "Command not implemented.");
		return false;
	}
	handler->second(param);
	lastCommand_ = cmd;
	return true;
}

FTPSession::~FTPSession()
{
	file_.reset();
	file_ = nullptr;
	dataConn_.reset();
	dataConn_ = nullptr;
	cmdConn_.reset();
	cmdConn_ = nullptr;
	if (buffer_)delete[] buffer_;
}

bool FTPSession::handleUSER(const std::string& param)
{
	if (checkLoginState()) {
		send(ReplyCode::REQUESTED_ACTION_ABORTED, "User has login.");
		return true;
	}
	bool existed = true;
	// todo: check if user is exsit by sqlite?
	if (existed) {
		user_->setUsername(param);
		loginState_ = State::LOGINING;
		send(ReplyCode::USER_NAME_OK,"Please enter password");
		return true;
	}
	else {
		send(ReplyCode::SYNTAX_ERROR_PARAMETERS,"Unknown username");
		return false;
	}
}

bool FTPSession::handlePASS(const std::string& param)
{
	bool pass = true;
	// todo: check if user is exsit from sqlite?
	// if exsited and password is right, then send login successfully!
	// else send login failed!
	if (checkLoginState()) {
		send(ReplyCode::REQUESTED_ACTION_ABORTED, "User has login.");
		return true;
	}
	if (lastCommand_ != "USER" || lastCommand_.empty()) {
		send(ReplyCode::BAD_SEQUENCE_OF_COMMANDS, "Bad sequence of command");
		return false;
	}
	if (pass) {
		user_->setPassword(param);
		std::string userRootDir = fmt::format("{}/{}/", std::filesystem::current_path().string(), user_->getUsername());
		user_->setRootDir(userRootDir);
		loginState_ = State::LOGINED;
		// todo: set permission from sqlite here ???
		send(ReplyCode::USER_LOGGED_IN,"Login successful");
		return true;
	}
	else {
		send(ReplyCode::NOT_LOGGED_IN,"Error password");
		return false;
	}
}

bool FTPSession::handlePASV(const std::string& param)
{
	// todo:每次PASV指令返回随机端口
	if (!checkLoginState())return false;
	if (evconnlistener_enable(dataAcceptor_.get()) != 0) {
		send(ReplyCode::REQUESTED_ACTION_ABORTED, "Error entering PASV mode.");
		LOG_ERROR << "Error entering PASV mode.";
		return false;
	}

	socket_t listenfd = evconnlistener_get_fd(dataAcceptor_.get());
	sockaddr_in listenAddr;
	SockLen len = sizeof(listenAddr);
	assert(getsockname(listenfd,(sockaddr*)&listenAddr,&len) == 0);
	char buf[64] = { 0 };
	if (::inet_ntop(AF_INET, &(listenAddr.sin_addr), buf, sizeof(buf)) == NULL)
	{
		perror("inet_ntop");
		send(ReplyCode::REQUESTED_ACTION_ABORTED, "Local error in processing.");
		return false;
	}
	std::string listenIp = buf;
	uint16_t listenPort = ::ntohs(listenAddr.sin_port);
	std::string passiveMsg = fmt::format("Entering Passive Mode ({}:{})", listenIp, listenPort);
	send(ReplyCode::ENTERING_PASSIVE_MODE, passiveMsg);
	return true;
}

bool FTPSession::handleMODE(const std::string& param)
{
	if (!checkLoginState())return false;
	std::string mode = param;
	std::for_each(mode.begin(), mode.end(), [](char& c) {c = std::toupper(c); });
	if (mode == "STREAM" || mode == "") {
		send(ReplyCode::COMMAND_OK, "Switching to STREAM mode.");
		return true;
	}
	send(ReplyCode::COMMAND_NOT_IMPLEMENTED, "Command not implemented.");
	return false;
}

bool FTPSession::handleTYPE(const std::string& param)
{
	if (!checkLoginState())return false;
	std::string type = param;
	std::for_each(type.begin(), type.end(), [](char& c) {c = std::toupper(c); });
	if (type == "I" || type == "") {
		send(ReplyCode::COMMAND_OK, "Switching to Binary type.");
		return true;
	}
	send(ReplyCode::SYNTAX_ERROR_PARAMETERS, "Param unrecognized.");
	return true;
}

bool FTPSession::handleSTRU(const std::string& param)
{
	if (!checkLoginState())return false;
	std::string structure = param;
	std::for_each(structure.begin(), structure.end(), [](char& c) {c = std::toupper(c); });
	if (structure == "F" || structure == "") {
		send(ReplyCode::COMMAND_OK, "Switching to file structure.");
		return true;
	}
	send(ReplyCode::SYNTAX_ERROR_PARAMETERS, "Param unrecognized.");
	return true;
}

bool FTPSession::handleLIST(const std::string& param)
{
	// todo: 如果数据连接未建立，返回错误码提醒需要建立数据连接
	if (!checkLoginState())return false;
	if (!checkPermission(param))return false;
	send(ReplyCode::COMMAND_NOT_IMPLEMENTED, "Command not implemented.");
	return false;
}

bool FTPSession::handleSTOR(const std::string& param)
{
	if (!checkLoginState())return false;
	// 如果数据连接未建立，返回错误码提醒需要建立数据连接
	if (!dataConn_) {
		send(ReplyCode::NOT_OPEN_DATA_CONNECTION, "Data connection not open.");
		return false;
	}
	// 正在传输其他文件，禁止传输
	if (isTransffering_)
	{
		send(ReplyCode::NOT_OPEN_DATA_CONNECTION, "Data connection is transffering.");
		return false;
	}
	std::string filePath = user_->getRootDir() + "/" + param;
	// todo: check filePath permission
	if (!checkPermission(param))return false;
	//if (std::filesystem::exists(filePath)) { // 文件已经存在
	//	send(ReplyCode::FILE_NOT_FIND_OR_NO_ACCESS, "File has exsited.");
	//	return false;
	//}
	if (!file_->open(filePath, "ab")) {
		send(ReplyCode::FILE_NOT_FIND_OR_NO_ACCESS, "Access file failed.");
		file_->close(); // 打开失败，关闭文件
		return false;
	}
	send(ReplyCode::REQUESTED_FILE_ACTION_OK, "Start store file.");
	dataConn_->enableRead();
	dataConn_->setMessageCallback(std::bind(&FTPSession::readFileCallback, this, std::placeholders::_1,std::placeholders::_2));
	isTransffering_ = true;
	fileStateMap_[file_->filename_] = FileState::Writing;
	return true;
}

bool FTPSession::handleRETR(const std::string& param)
{
	if (!checkLoginState())return false;
	// todo: 如果数据连接未建立，返回错误码提醒需要建立数据连接
	if (!dataConn_) {
		send(ReplyCode::NOT_OPEN_DATA_CONNECTION, "Data connection not open.");
		return false;
	}
	if (isTransffering_) // 正在传输其他文件，禁止传输
	{
		send(ReplyCode::NOT_OPEN_DATA_CONNECTION, "Data connection is transffering.");
		return false;
	}
	std::string filePath = user_->getRootDir() + "/" + param;
	// todo: check filePath permission
	if (!checkPermission(filePath))return false; // checkPermisson函数需要修改
	if (!std::filesystem::exists(filePath)) {
		send(ReplyCode::FILE_NOT_FIND_OR_NO_ACCESS, "File not find.");
		return false;
	}
	// 检查文件是否被写
	if (fileStateMap_.find(filePath) != fileStateMap_.end() && (fileStateMap_[filePath] & FileState::Writing)) {
		send(ReplyCode::FILE_NOT_FIND_OR_NO_ACCESS, "File access failed.");
		return false;
	}
	if (!file_->open(filePath, "rb")) {
		send(ReplyCode::FILE_NOT_FIND_OR_NO_ACCESS, "Access file failed.");
		file_->close(); // 打开失败，关闭文件
		return false;
	}
	send(ReplyCode::REQUESTED_FILE_ACTION_OK, "Start retrieve file.");
	dataConn_->enableWrite();
	dataConn_->setWriteCompleteCallback(std::bind(&FTPSession::writeFileCallback, this, std::placeholders::_1));
	startSendFile();
	fileStateMap_[file_->filename_] = FileState::Reading;
	isTransffering_ = true;
	return true;
}

bool FTPSession::handleSIZE(const std::string& param)
{
	if (!checkLoginState())return false;
	std::string filePath = user_->getRootDir() + "/" + param;
	// todo: check filePath permission
	if (!checkPermission(filePath))return false; // checkPermisson函数需要修改
	if (!std::filesystem::exists(filePath)) {
		send(ReplyCode::FILE_NOT_FIND_OR_NO_ACCESS, "File not find.");
		return false;
	}
	size_t fileSize = std::filesystem::file_size(filePath);
	send(ReplyCode::FILE_STATUS, std::to_string(fileSize));
	return true;
}

bool FTPSession::handleREST(const std::string& param)
{
	if (!checkLoginState())return false;
	size_t offset = 0;
	try {
		offset = std::stoll(param);
	}
	catch (const std::exception& err) {
		send(ReplyCode::SYNTAX_ERROR_PARAMETERS, "Error param.");
		return false;
	}
	file_->setOffset(offset);
	std::string msg = fmt::format("Restarting at {}. Send STOR or RETR to initiate transfer.", offset);
	send(ReplyCode::FILE_ACTION_NEED_MORE_INFORMATION, msg);
	return true;
}

bool FTPSession::handlePWD(const std::string& param)
{
	if (!checkLoginState())return false;
	std::cout << std::filesystem::current_path().c_str() << std::endl;
	std::string currDir = std::filesystem::current_path().string();
	send(ReplyCode::REQUESTED_FILE_ACTION_OK, currDir);
	return true;
}

bool FTPSession::handleNOOP(const std::string& param)
{
	send(ReplyCode::COMMAND_OK, "Command ok.");
	return true;
}


bool FTPSession::handleMKD(const std::string& param)
{
	send(ReplyCode::COMMAND_NOT_IMPLEMENTED, "Command not implemented.");
	return false;
}

bool FTPSession::handleRNFR(const std::string& param)
{
	return false;
}

bool FTPSession::handleRNTO(const std::string& param)
{
	send(ReplyCode::COMMAND_NOT_IMPLEMENTED, "Command not implemented.");
	return false;
}

bool FTPSession::handleDELE(const std::string& param)
{
	if (!checkLoginState())return false;
	std::string filePath = user_->getRootDir() + "/" + param;
	// todo: check filePath permission
	if (!checkPermission(filePath)) {
		send(ReplyCode::FILE_NOT_FIND_OR_NO_ACCESS, "File delete permission denied.");
		return false; // checkPermisson函数需要修改
	}
	if (!std::filesystem::exists(filePath)) {
		send(ReplyCode::FILE_NOT_FIND_OR_NO_ACCESS, "File not find.");
		return false;
	}
	if (fileStateMap_.find(filePath) != fileStateMap_.end() && (fileStateMap_[filePath] & FileState::ReadingOrWriting) != 0) {
		send(ReplyCode::FILE_NOT_FIND_OR_NO_ACCESS, "Flie delete failed, file is wrote or read.");
		return false;
	}
	try {
		std::filesystem::remove(filePath);
	}
	catch (const std::filesystem::filesystem_error& error) {
		std::string errMsg = fmt::format("{} try to remove file {} failed. msg: {}", name(), filePath, error.what());
		LOG_ERROR << errMsg;
		send(ReplyCode::FILE_NOT_FIND_OR_NO_ACCESS, "File delete denied.");
		return false;
	}
	send(ReplyCode::REQUESTED_FILE_ACTION_OK, "File delete successfully.");
	return true;
}

bool FTPSession::handleHELP(const std::string& param)
{
	send(ReplyCode::COMMAND_NOT_IMPLEMENTED, "Command not implemented.");
	return false;
}
